package com.demo.demo_scan;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;

public class OpencvMainApp {

	static {
		String dir = System.getProperty("user.dir").replace("/", "\\");
		String filePath = "\\lib\\" + (System.getProperty("java.vm.name").contains("64") ? "x64" : "x86")
				+ "\\opencv_java455.dll";
		String path = dir.concat(filePath);
		File file = new File(path);
		if (!file.exists()) {
			throw new RuntimeException("找不到动态库".concat(path));
		}
		System.load(path);
		System.out.println(path.concat("  load success"));
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		String fileName = "QRj3";
		String dir = System.getProperty("user.dir").replace("/", "\\");
		String imgUrl = dir.concat("\\img\\" + fileName + ".jpg");
		Mat src = Imgcodecs.imread(imgUrl, 1);
		
		String result = opencvScanCapture(src, fileName);
		System.out.println(result);

		if (!result.startsWith("识别失败")) {

			try {
				File imageFile = new File(result);
				BufferedImage bufferedImage = ImageIO.read(imageFile);
				Result rqResult = decodeQRCode(bufferedImage);
				if (rqResult != null) {
					// 识别结果
					System.out.println("Decoded text: " + rqResult.getText());
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	private static Result decodeQRCode(BufferedImage image) throws NotFoundException {
		LuminanceSource source = new BufferedImageLuminanceSource(image);
		BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
		Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
		hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
		return new MultiFormatReader().decode(bitmap, hints);
	}

	public static String imwrite(Mat mat, String fileName) {
		String dir = System.getProperty("user.dir").replace("/", "\\");
		String imgUrl = dir.concat("\\img\\" + fileName + ".jpg"); // 生成写入灰色图片
		Imgcodecs.imwrite(imgUrl, mat);
		return imgUrl;
	}

	public static String opencvScanCapture(Mat src, String fileName) {
		Mat src_gray = new Mat(); // 定义

		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
		/** 图片太小就放大 **/
		if (src.width() * src.height() < 90000) {
			Imgproc.resize(src, src, new Size(800, 600));
		}
		Mat src_all = src.clone();
		// 彩色图转灰度图
		Imgproc.cvtColor(src, src_gray, Imgproc.COLOR_RGB2GRAY);

		// 对图像进行平滑处理
		Imgproc.GaussianBlur(src_gray, src_gray, new Size(5, 5), -1);
//		Imgproc.GaussianBlur(src_gray, src_gray, new Size(3, 3), 5);
		Imgproc.Canny(src_gray, src_gray, 112, 255);

		// 生成写入灰色图片
		imwrite(src_gray, fileName + "-gray");

		// 找到所有的轮廓
		Mat hierarchy = new Mat();
		Imgproc.findContours(src_gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);

		// 遍历所有找到的轮廓
		for (int i = 0; i < contours.size(); i++) {
			MatOfPoint2f newMtx = new MatOfPoint2f(contours.get(i).toArray());
			RotatedRect rotRect = Imgproc.minAreaRect(newMtx);
			double w = rotRect.size.width;
			double h = rotRect.size.height;
			double rate = Math.max(w, h) / Math.min(w, h);
			/***
			 * 长短轴比小于1.3，总面积大于60
			 */
			if (rate < 1.3 && w < src_gray.cols() / 4 && h < src_gray.rows() / 4
					&& Imgproc.contourArea(contours.get(i)) > 60) {
				/***
				 * 计算层数，二维码角框有五层轮廓（有说六层），这里不计自己这一层，有4个以上子轮廓则标记这一点
				 */
				// 获取当前轮廓的层级信息
				double[] ds = hierarchy.get(0, i);
				if (ds != null && ds.length > 3) {
					int count = 0;
					if (ds[3] == -1) {/** 最外层轮廓排除 */
						continue;
					}

					/***
					 * 计算所有子轮廓数量
					 */
					while ((int) ds[2] != -1) {
						++count;
						ds = hierarchy.get(0, (int) ds[2]);
					}

					if (count >= 4) {
						markContours.add(contours.get(i));
					}
				}
			}
		}
		/**
		 * 这部分代码画框，调试用
		 **/
		for (int i = 0; i < markContours.size(); i++) {
			Imgproc.drawContours(src_all, markContours, i, new Scalar(0, 255, 0), -1);
		}
		// 输出轮廓画框图
		imwrite(src_all, fileName + "-contour");

		/***
		 * 二维码有三个角轮廓，少于三个的无法定位放弃，多余三个的循环裁剪出来
		 */
		if (markContours.size() < 3) {
			return "识别失败-未找到合适的轮廓";
		}
		List<String> fileResults = new ArrayList<String>();
		// 循环裁剪 输出图片地址
		for (int i = 0; i < markContours.size() - 2; i++) {
			List<MatOfPoint> threePointList = new ArrayList<>();
			for (int j = i + 1; j < markContours.size() - 1; j++) {
				for (int k = j + 1; k < markContours.size(); k++) {
					threePointList.add(markContours.get(i));
					threePointList.add(markContours.get(j));
					threePointList.add(markContours.get(k));
					fileResults.add(capture(threePointList, src, fileName, (i + "-" + j + "-" + k)));
					threePointList.clear();
				}
			}
		}

		if (fileResults.isEmpty()) {
			return "识别失败-识别错误";
		}
		// 最后一个结果
		return fileResults.get(fileResults.size() - 1);
	}

	/**
	 * 对图片进行矫正，裁剪
	 * 
	 * @param contours 描点画框轮廓
	 * @param src
	 * @param fileName 文件名
	 * @param idx
	 */
	public static String capture(List<MatOfPoint> contours, Mat src, String fileName, String idx) {
		Point[] pointthree = new Point[3];
		for (int i = 0; i < contours.size(); i++) {
			pointthree[i] = centerCal(contours.get(i));
		}

		/**
		 * 画线
		 **/
		Mat sline = src.clone();
		Imgproc.line(sline, pointthree[0], pointthree[1], new Scalar(0, 0, 255), 2);
		Imgproc.line(sline, pointthree[1], pointthree[2], new Scalar(0, 0, 255), 2);
		Imgproc.line(sline, pointthree[0], pointthree[2], new Scalar(0, 0, 255), 2);

		// 画线图
		imwrite(sline, fileName + "-sline-" + idx);

		double[] ca = new double[2];
		double[] cb = new double[2];

		ca[0] = pointthree[1].x - pointthree[0].x;
		ca[1] = pointthree[1].y - pointthree[0].y;
		cb[0] = pointthree[2].x - pointthree[0].x;
		cb[1] = pointthree[2].y - pointthree[0].y;
		/*
		 * if (Math.max(ca[0],cb[0])/Math.min(ca[0],cb[0]) > 1.5 ||
		 * Math.max(ca[1],cb[1])/Math.min(ca[1],cb[1])>1.3){ return; }
		 */
		double angle1 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		double ccw1;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw1 = 0;
		} else {
			ccw1 = 1;
		}
		ca[0] = pointthree[0].x - pointthree[1].x;
		ca[1] = pointthree[0].y - pointthree[1].y;
		cb[0] = pointthree[2].x - pointthree[1].x;
		cb[1] = pointthree[2].y - pointthree[1].y;
		double angle2 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		double ccw2;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw2 = 0;
		} else {
			ccw2 = 1;
		}

		ca[0] = pointthree[1].x - pointthree[2].x;
		ca[1] = pointthree[1].y - pointthree[2].y;
		cb[0] = pointthree[0].x - pointthree[2].x;
		cb[1] = pointthree[0].y - pointthree[2].y;
		double angle3 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
				/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
		int ccw3;
		if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
			ccw3 = 0;
		} else {
			ccw3 = 1;
		}

		System.out.println("angle1:" + angle1 + ",angle2:" + angle2 + ",angle3:" + angle3);
		if (Double.isNaN(angle1) || Double.isNaN(angle2) || Double.isNaN(angle3)) {
			return "识别失败-未识别出合适的角度";
		}

		Point[] poly = new Point[4];
		if (angle3 > angle2 && angle3 > angle1) {
			if (ccw3 == 1) {
				poly[1] = pointthree[1];
				poly[3] = pointthree[0];
			} else {
				poly[1] = pointthree[0];
				poly[3] = pointthree[1];
			}
			poly[0] = pointthree[2];
			Point temp = new Point(pointthree[0].x + pointthree[1].x - pointthree[2].x,
					pointthree[0].y + pointthree[1].y - pointthree[2].y);
			poly[2] = temp;
		} else if (angle2 > angle1 && angle2 > angle3) {
			if (ccw2 == 1) {
				poly[1] = pointthree[0];
				poly[3] = pointthree[2];
			} else {
				poly[1] = pointthree[2];
				poly[3] = pointthree[0];
			}
			poly[0] = pointthree[1];
			Point temp = new Point(pointthree[0].x + pointthree[2].x - pointthree[1].x,
					pointthree[0].y + pointthree[2].y - pointthree[1].y);
			poly[2] = temp;
		} else if (angle1 > angle2 && angle1 > angle3) {
			if (ccw1 == 1) {
				poly[1] = pointthree[1];
				poly[3] = pointthree[2];
			} else {
				poly[1] = pointthree[2];
				poly[3] = pointthree[1];
			}
			poly[0] = pointthree[0];
			Point temp = new Point(pointthree[1].x + pointthree[2].x - pointthree[0].x,
					pointthree[1].y + pointthree[2].y - pointthree[0].y);
			poly[2] = temp;
		}

		Point[] trans = new Point[4];

//		int temp = 50;
//		trans[0] = new Point(0 + temp, 0 + temp);
//		trans[1] = new Point(0 + temp, 100 + temp);
//		trans[2] = new Point(100 + temp, 100 + temp);
//		trans[3] = new Point(100 + temp, 0 + temp);
		trans[0] = new Point(25, 25); //左上角
		trans[1] = new Point(25, 175); //左下角
		trans[2] = new Point(175, 175); //右下角
		trans[3] = new Point(175, 25); //右上角

		double maxAngle = Math.max(angle3, Math.max(angle1, angle2));
		System.out.println(maxAngle);
		if (maxAngle < 75 || maxAngle > 115) { /** 二维码为直角，最大角过大或者过小都判断为不是二维码 */
			return "识别失败-角度过大或国小，可能是侧向拍摄";
		}

		Mat perspectiveMmat = Imgproc.getPerspectiveTransform(
				Converters.vector_Point_to_Mat(Arrays.asList(poly), CvType.CV_32F),
				Converters.vector_Point_to_Mat(Arrays.asList(trans), CvType.CV_32F)); // warp_mat
		Mat dst = new Mat();
		// 计算变换结果
		Imgproc.warpPerspective(src, dst, perspectiveMmat, src.size(), Imgproc.INTER_LINEAR);

		// 二值化处理
		Mat dst2 = new Mat();
		Imgproc.threshold(dst, dst2, 128, 255, Imgproc.THRESH_BINARY);
		
		Rect roiArea2 = new Rect(0, 0, 200, 200);
		Mat dstRoi2 = new Mat(dst2, roiArea2);
		imwrite(dstRoi2, fileName + "-result-threshold-" + idx);

		// 200大小
		Rect roiArea = new Rect(0, 0, 200, 200);
		Mat dstRoi = new Mat(dst, roiArea);
		
		// 放大亮度
		Mat enhanced = new Mat();
		Core.convertScaleAbs(dstRoi, enhanced, 1.5, 10); // 增加对比度和亮度
		imwrite(enhanced, fileName + "-result-ld-" + idx);
		
		// 400
		Mat resized = new Mat();
		Imgproc.resize(enhanced, resized, new org.opencv.core.Size(400, 400), 0, 0, Imgproc.INTER_CUBIC);
		imwrite(resized, fileName + "-result-400-" + idx);
		
		imwrite(dstRoi, fileName + "-result-" + idx);
		return imwrite(enhanced, fileName + "-result-ld-" + idx);
	}

	public static BufferedImage toBufferedImage(Mat m) {
		int type = BufferedImage.TYPE_BYTE_GRAY;

		if (m.channels() > 1) {
			type = BufferedImage.TYPE_3BYTE_BGR;
		}

		int bufferSize = m.channels() * m.cols() * m.rows();
		byte[] b = new byte[bufferSize];
		m.get(0, 0, b); // get all the pixels
		BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);

		final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
		System.arraycopy(b, 0, targetPixels, 0, b.length);

		return image;
	}

	public static Point centerCal(MatOfPoint matOfPoint) {
		double centerx = 0, centery = 0;
//		int size = matOfPoint.cols();
		MatOfPoint2f mat2f = new MatOfPoint2f(matOfPoint.toArray());
		RotatedRect rect = Imgproc.minAreaRect(mat2f);
		Point vertices[] = new Point[4];
		rect.points(vertices);
		centerx = ((vertices[0].x + vertices[1].x) / 2 + (vertices[2].x + vertices[3].x) / 2) / 2;
		centery = ((vertices[0].y + vertices[1].y) / 2 + (vertices[2].y + vertices[3].y) / 2) / 2;
		Point point = new Point(centerx, centery);
		return point;
	}

}
